package org.epsi.blokus.entity;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.epsi.blokus.Hex;
import org.epsi.blokus.tools.*;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.UnicodeFont;
import org.newdawn.slick.state.StateBasedGame;

/**
 * Gestion et affichage du plateau de jeu
 * @author julien.lafont
 */
public class Board implements IEntity, IConstantes {

    // Cases de départ et d'arrivée des joueurs
    private static Point[] BlueDepart = new Point[NB_CASES];
    private static Point[] BlueWin = new Point[NB_CASES];
    private static Point[] RedDepart = new Point[NB_CASES];
    private static Point[] RedWin = new Point[NB_CASES];
    static {
       for (int i=0; i<NB_CASES; i++)
       {
           BlueDepart[i] = new Point(i, 0);
           BlueWin[i] = new Point(i, NB_CASES-1);
           RedDepart[i] = new Point(0, i);
           RedWin[i] = new Point(NB_CASES-1, i);
       }
    }

    // Ressources
    private static Image bgWin = null;
    private static Image bgLoose = null;
    private static UnicodeFont fontTitre = FontsUtil.getFontShadow("lte50335", 36, Color.WHITE, Color.BLACK, 2, 2, 1f, false, false);
    private static UnicodeFont fontSsTitre = FontsUtil.getFontShadow("lte50331", 24, Color.WHITE, Color.BLACK, 1, 1, 1f, false, false);
    private static UnicodeFont fontTxt = FontsUtil.getFontShadow("lte50331", 20, Color.WHITE, Color.BLACK, 1, 1, 1f, false, false);


    // Gestion des cases et des pièces
    private List<IEntity> entities = Collections.synchronizedList(new ArrayList<IEntity>());
    private TTypePiece[][] board = new TTypePiece[NB_CASES][NB_CASES];
    private int nbPiecePosees = 0;

    // Affichage de la case survolée
    private Piece selectCase;
    private boolean renderSelectCase = false;

    // Fin du jeu
    private boolean afficherVictoire = false;
    private boolean afficherEchec = false;
    
    public void init(GameContainer container, StateBasedGame game) throws SlickException {

        // Initialisation du plateau de jeu à vide
        for (int i=0; i<NB_CASES; i++) for (int j=0; j<NB_CASES; j++) this.board[i][j]=TTypePiece.Vide;

        // Piece spéciale : affichée au survol
        selectCase = new Piece(TTypePiece.Select, 0, 0);

        // Initialisation des ressources
        if (bgWin==null)
        {
            bgWin = new Image("/res/Dialogs/winner.png");
            bgLoose = new Image("/res/Dialogs/looser.png");
        }

    }


    public void update(GameContainer container, StateBasedGame game, int delta) throws SlickException {
        synchronized(entities) {
            for(IEntity entity : entities) entity.update(container, game, delta);
            if (renderSelectCase) selectCase.update(container, game, delta);
        }

    }


    public void render(GameContainer container, StateBasedGame game, Graphics g) throws SlickException {
        synchronized(entities) {
            for(IEntity entity : entities) entity.render(container, game, g);
            if (renderSelectCase) selectCase.render(container, game, g);
        };

        // Affiche message de victoire
        if (afficherVictoire)
        {
            bgWin.draw(324, 155);

            g.setFont(fontTitre);
            g.drawString("Félicitations", FontsUtil.getX_CenteredText("Félicitations", fontTitre, 350, 350), 180);

            g.setFont(fontSsTitre);
            g.drawString("Vous êtes le grand gagnant", 350, 244);
            g.drawString("de cette partie d'Hex !", 350, 280);
        }

        // Affiche message d'échec
        if (afficherEchec)
        {
            bgLoose.draw(324,155);

            g.setFont(fontTitre);
            g.drawString("Echec !", FontsUtil.getX_CenteredText("Echec !", fontTitre, 350, 350), 180);

            g.setFont(fontSsTitre);
            g.drawString("Votre adversaire vous a ", 350, 244);
            g.drawString("terrasé sur cette partie !", 350, 280);
        }

        // Message commun
        if (afficherEchec || afficherVictoire)
        {
            g.setFont(fontTxt);
            g.drawString("Cliquez pour continuer", 474, 390);
        }
        
    }

    /**
     * Ajouter une pièce au plateau
     * @param col
     * @param lig
     * @param type
     * @throws SlickException
     */
    public void addPiece(int col, int lig, Piece.TTypePiece type) throws SlickException
    {
        Piece p = new Piece(type, CaseUtil.caseToCoord(col, lig));
        this.entities.add(p);
        this.board[lig][col] = type;
        nbPiecePosees++;
    }


    /**
     * Une case est-elle libre ?
     * @param col
     * @param lig
     * @return
     */
    public boolean isFree(int col, int lig)
    {
        return this.board[lig][col] == TTypePiece.Vide;
    }


    /**
     * Partie finie ?
     */
    public boolean isFinished()
    {
        return nbPiecePosees == NB_CASES*NB_CASES;
    }

    
    /**
     * Vérifie si un joueur a gagné la partie
     * @param color : Couleur du joueur
     * @return Vrai si le joueur a gagné la partie, faux sinon
     * ( ce qui ne veut pas dire qu'il a forcément perdu ! )
     */
    public boolean checkWin(TTypePiece color){
        Point from[] = new Point[NB_CASES];
        Point to[] = new Point[NB_CASES];
        boolean checked[][] = new boolean[NB_CASES][NB_CASES]; // Faux par défaut (norme SUN)

        // Cases de départ et d'arrivée en fonction de la couleur
        if (color == TTypePiece.Bleu)
        {
            from = BlueDepart;
            to = BlueWin;
        }
        else
        {
            from = RedDepart;
            to = RedWin;
        }

        Boolean gagne = false;

        // On étudie à partir de chaque point de départ tant qu'on a pas trouvé un chemin gagnant
        for (int i=0; i<NB_CASES && !gagne; i++)
        {
            gagne = checkWinLine(color, from[i], to, checked);
        }

        return gagne;

    }


    /**
     * Fonction récursive vérifiant si une branche de l'arbre formée par les jetons est victorieuse
     * @param type : Couleur à vérifier
     * @param from : Case de départ
     * @param to : Liste des cases victorieuses
     * @param checked : Tableau des cases déjà vérifiées
     * @return Si le joueur est gagnant ou non sur la branche étudiée
     */
    private Boolean checkWinLine(TTypePiece type, Point from, Point[] to, boolean[][] checked) {

        // Case en dehors du jeu : STOP
        if (from.getX()<0 || from.getX() > (NB_CASES-1) || from.getY()<0 || from.getY() > (NB_CASES-1) ) return false;

        // Si la case n'est pas de la bonne couleur : STOP
        else if (this.board[from.getY()][from.getX()] != type) return false;

        // Case déjà examinée : STOP
        else if (checked[from.getY()][from.getX()]) return false;
        
        else
        {

            // Le point étudié est un point de destination : GAGNE
            for (int i=0; i<NB_CASES; i++)  if (from.compareTo(to[i])>=0) return true;
          
            // On marque la case en cours comme étant étudiée
            checked[from.getY()][from.getX()] = true;

            // Sinon on étudie récursivement les cases voisines
            return checkWinLine(type, from.alter(-1, 0), to, checked)         // Gauche
                    || checkWinLine(type, from.alter(+1, 0), to, checked)     // Droite
                    || checkWinLine(type, from.alter(0, -1), to, checked)     // Haut Gauche
                    || checkWinLine(type, from.alter(+1, -1), to, checked)    // Haut Droite
                    || checkWinLine(type, from.alter(-1, +1), to, checked)    // Bas Gauche
                    || checkWinLine(type, from.alter(0, +1), to, checked);    // Bas Droite
            
        }
    }

    /**
     * Compte le nombre de voisins "ennemis" d'une case donnée
     * @param c
     * @param l
     * @param couleur
     * @return
     */
    public int countNeighbor(int c, int l, TTypePiece couleur)
    {
        int nb=0;
        if (l-1>=0 && this.board[l-1][c]==couleur) nb++;                    // Haut Gauche
        if (c+1<=(NB_CASES-1) && l-1>=0 && this.board[l-1][c+1]==couleur) nb++;       // Haut Droite
        if (c+1<=(NB_CASES-1) && this.board[l][c+1]==couleur) nb++;                   // Droite
        if (l+1<=(NB_CASES-1) && this.board[l+1][c]==couleur) nb++;                   // bas Droite
        if (c-1>=0 && l+1<=(NB_CASES-1) && this.board[l+1][c-1]==couleur) nb++;       // Bas Gauuche
        if (c-1>=0 && this.board[l][c-1]==couleur) nb++;                    // Gauche

        return nb;
    }


    /**
     * Déplacement de la souris sur la grille : on affiche la case "surlignée"
     * si il n'y a pas de jeton sur la case.
     * @param oldx
     * @param oldy
     * @param newx
     * @param newy
     */
    public void mouseMoved(int oldx, int oldy, int newx, int newy) {

        // On affiche le curseur que si c'est au tour du joueur local
        if (Hex.getInstance().getPlayer().getState() == INGAME_STATES.Play)
        {
            try {
                
                Point pos = CaseUtil.coordToCase(newx, newy);

                if (isFree(pos.getX(), pos.getY())) {
                    this.renderSelectCase = true;
                    this.selectCase.setPosition(CaseUtil.caseToCoord(pos.getX(),pos.getY()));
                }
                else
                {
                    this.renderSelectCase = false;
                }

            } catch(SlickException ex) { 
                this.renderSelectCase = false;
            }
        }
    }


    public void afficherVictoire()
    {
        afficherVictoire = true;
    }

    public void afficherDefaite()
    {
        afficherEchec = true;
    }





    

}
